package com.by.order.service.impl;

import com.by.rpc.util.RedisLuaHelper;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.UUID;

@Slf4j
@Service
public class SlidingWindowRateLimiter {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 注入 RedisLuaHelper
    @Autowired
    private RedisLuaHelper redisLuaHelper;

    private RedisScript<Long> script;

    /**
     * Lua脚本，用于实现滑动窗口限流的原子操作。
     * KEYS[1]: 窗口 key
     * ARGV[1]: 窗口大小 (秒)
     * ARGV[2]: 限制数量
     * ARGV[3]: 当前时间戳 (毫秒)
     * ARGV[4]: 唯一请求 ID
     */
    private static final String LUA_SCRIPT = """
                local key = KEYS[1]
                local window = tonumber(ARGV[1])
                local limit = tonumber(ARGV[2])
                -- 修复：使用更健壮的方式将字符串转换为数字，避免因序列化问题导致的nil值
                local current_time = ARGV[3] + 0
                local member_id = ARGV[4]

                -- 修复：添加对当前时间的有效性检查，以防出现nil值
                if current_time == 0 then
                    return -1
                end

                -- 移除窗口外（过时）的请求记录，将秒转换为毫秒
                -- scoreStart = 0, scoreEnd = current_time - window * 1000
                redis.call('zremrangebyscore', key, 0, current_time - window * 1000)

                -- 2. 获取当前窗口内的请求数
                local count = redis.call('zcard', key)

                -- 3. 判断是否超过限流阈值
                if count < limit then
                    -- 4. 未超过，添加本次请求记录，并设置过期时间
                    redis.call('zadd', key, current_time, member_id)
                    -- 设置过期时间，避免Key永久存在，这里设置过期时间为窗口大小的两倍
                    -- 过期时间也需要转换为秒
                    redis.call('expire', key, window * 2)
                    return 1
                else
                    -- 5. 超过限流，返回0
                    return 0
                end
            """;

    @PostConstruct
    public void init() {
        script = new DefaultRedisScript<>(LUA_SCRIPT, Long.class);
    }

    /**
     * 尝试获取令牌（尝试放行一个请求）。
     *
     * @param key             限流键
     * @param windowInSeconds 窗口大小（秒）
     * @param limit           窗口内的最大请求数
     * @return true 如果请求被放行，false 如果被限流
     */
    public boolean tryAcquire(String key, int windowInSeconds, int limit) {
        try {
            long currentTimeMillis = System.currentTimeMillis();
            String memberId = UUID.randomUUID().toString();

            // 修复：使用 RedisLuaHelper 来执行 Lua 脚本
            Long result = redisLuaHelper.executeStringLua(
                    (DefaultRedisScript<Long>) script,
                    Collections.singletonList(key),
                    String.valueOf(windowInSeconds),
                    String.valueOf(limit),
                    String.valueOf(currentTimeMillis),
                    memberId
            );

            // 修复：处理脚本返回的错误码-1
            if (result != null && result == -1) {
                log.error("滑动窗口限流脚本执行失败，传入的参数不合法。key: {}", key);
                return false;
            }

            return result != null && result == 1;
        } catch (Exception e) {
            log.error("执行滑动窗口限流脚本失败，key: {}", key, e);
            // 默认采取安全策略：在限流器异常时拒绝请求，防止系统过载
            return false;
        }
    }
}
